package com.googlecode.continuity.core.server.hibernate;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.sf.hibernate4gwt.core.HibernateBeanManager;
import net.sf.hibernate4gwt.core.HibernateHelper;
import net.sf.hibernate4gwt.core.LazyKiller;
import net.sf.hibernate4gwt.core.beanlib.IClassMapper;
import net.sf.hibernate4gwt.exception.NotAssignableException;
import net.sf.hibernate4gwt.exception.TransientHibernateObjectException;

/**
 * A HibernateBeanManager implementation that allows for new domain objects to be created on the client side (GWT) even
 * if their class is stored with Hibernate. Also utilizes a custom LazyKiller that fixes issues with Enum cloning.
 *
 * @author Peter Schmitt <aragos@aragos.de>
 */
public class CustomBeanManager extends HibernateBeanManager {
  private LazyKiller lazyKiller;

  /**
   * Log channel
   */
  protected Log log = LogFactory.getLog(HibernateBeanManager.class);

  /**
   * Creates a new CustomBeanManager which uses a CustomLazyKiller.
   */
  public CustomBeanManager() {
    lazyKiller = new CustomLazyKiller();
  }

  /**
   * Retrieve the Hibernate Pojo and merge the modification from GWT
   * Copied and modified from HibernateBeanManager
   *
   * @param clonePojo  the clone pojo
   * @param assignable does the source and target class must be assignable
   * @return the merged Hibernate POJO
   * @throws UnsupportedOperationException if the clone POJO does not
   *                                       implements ILazyPojo and the POJO store is stateless
   * @throws net.sf.hibernate4gwt.exception.NotAssignableException
   *                                       if source and target class are not assignable
   */
  protected Object mergePojo(Object clonePojo, boolean assignable) {
    //	Get Hibernate associated class
    //
    Class cloneClass = clonePojo.getClass();
    Class hibernateClass = getClassMapper().getSourceClass(cloneClass);
    if (hibernateClass == null) {
      //	Not a clone : take the inner class
      //
      hibernateClass = clonePojo.getClass();
    }

    //	Precondition checking
    //
    if (!HibernateHelper.getInstance().isHibernateClass(hibernateClass)) {
      //	Not an Hibernate class : no merge needed
      //
      return clonePojo;
    }

    //	Assignation checking
    //
    if (assignable && !hibernateClass.isAssignableFrom(cloneClass)) {
      throw new NotAssignableException(hibernateClass, cloneClass);
    }

    //	Retrieve the pojo
    //
    Object hibernatePojo = null;
    getPojoStore().beforeRestore();
    try {
      hibernatePojo = getPojoStore().restore(clonePojo, hibernateClass);
      if (hibernatePojo == null) {
        log.warn("HibernatePOJO not found : can be transient or deleted data : " + clonePojo);
      }
    }
    catch (TransientHibernateObjectException ex) {
      log.info("Transient object : no merge needed  _ " + clonePojo);
    }

    if (hibernatePojo == null) {
      if (hibernateClass.equals(clonePojo.getClass())) {
        //	Not a proxy : we can return the clone pojo
        //
        return clonePojo;
      } else {
        //	Proxy : generate a new Hibernate pojo and merge the proxy with it
        //
        try {
          hibernatePojo = hibernateClass.newInstance();
        }
        catch (Exception e) {
          throw new RuntimeException("Cannot create a fresh new instance of the class " + hibernateClass, e);
        }
      }
    }

    //	Merge the modification in the Hibernate Pojo
    //
    lazyKiller.attach(hibernatePojo, clonePojo);
    getPojoStore().afterRestore();
    return hibernatePojo;
  }

  /**
   * Clone and store the Hibernate POJO
   * Copied and modified from HibernateBeanManager
   *
   * @param pojo       the pojo to store
   * @param assignable does the source and target class must be assignable?
   * @throws NotAssignableException if source and target class are not assignable
   */
  protected Object clonePojo(Object pojo, boolean assignable) {
    //	Precondition checking
    //
    if (!HibernateHelper.getInstance().isHibernatePojo(pojo)) {
      return pojo;
    }

    //	Assignation test
    //
    Class hibernateClass = pojo.getClass();
    Class targetClass = getClassMapper().getTargetClass(hibernateClass);
    if (targetClass == null) {
      targetClass = hibernateClass;
    }

    if (assignable && !hibernateClass.isAssignableFrom(targetClass)) {
      throw new NotAssignableException(hibernateClass, targetClass);
    }

    //	Store the pojo
    //
    getPojoStore().store(pojo);

    //	Clone the pojo
    //
    return lazyKiller.detach(pojo, targetClass);
  }

  /**
   * Sets a new class mapper on this BeanManager and its LazyKiller.
   *
   * @param classMapper new class mapper
   */
  public void setClassMapper(IClassMapper classMapper) {
    if (lazyKiller != null) {
      lazyKiller.setClassMapper(classMapper); 
    }
    super.setClassMapper(classMapper);
  }
}
